export class TinyImport {
  constructor(params = {}) {
    const { exclude, include } = this._handleFilter(params)

    this.globalExclude = exclude
    this.globalInclude = include
  }

  importAll(args) {
    const { exclude, include } = this._handleFilter(args)

    return this._import({
      ...args,
      exclude: (...params) =>
        exclude(...params) || this.globalExclude(...params),
      include: (...params) =>
        include(...params) && this.globalInclude(...params),
    })
  }

  importAllType({ fileType, ...params }) {
    return this.importAll({
      ...params,
      includePath: this._generateFileReg(fileType),
    })
  }

  importJs(params) {
    return this.importAllType({
      ...params,
      fileType: 'js',
    })
  }

  importImg(params) {
    return this.importAllType({
      ...params,
      fileType: 'img',
    })
  }

  importVue(params) {
    return this.importAllType({
      ...params,
      fileType: 'vue',
    })
  }

  importJson(params) {
    return this.importAllType({
      ...params,
      fileType: 'json',
    })
  }

  importTs(params) {
    return this.importAllType({
      ...params,
      fileType: 'ts',
    })
  }

  _import({
    context,
    exclude = () => false,
    include = () => true,
    key,
    value,
    noSub = false,
  }) {
    if (!context) {
      throw new Error('[TinyImport] expected a context!')
    }

    if (!this._isType(exclude, include, () => {})) {
      throw new Error('[TinyImport] exclude | include expected a function!')
    }

    if (!this._inType(key, [undefined, '', () => {}])) {
      throw new Error('[TinyImport] key expected a string or function!')
    }

    if (!this._inType(value, [undefined, () => {}])) {
      throw new Error('[TinyImport] value expected function!')
    }

    const fileList = this._formatFileList(context, noSub)

    if (!this._isType(fileList, [])) {
      throw new Error('[TinyImport] context type error!')
    }

    const ret = {}
    for (const path of fileList) {
      const name = this._getFileName(path)
      if (!name) {
        continue
      }

      if (exclude(name, path)) {
        continue
      }

      if (!include(name, path)) {
        continue
      }

      const fileName = key && key({ name, path }) ? key({ name, path }) : name
      const fileValue = value
        ? value({
            file: context(path),
            path,
            name,
            key: fileName,
          })
        : context(path)

      ret[fileName] = fileValue
    }
    return ret
  }

  _handleFilter({ exclude, include, excludePath, includePath }) {
    // check params
    this._checkFiltersType(exclude)
    this._checkFiltersType(include)
    this._checkFiltersType(excludePath)
    this._checkFiltersType(includePath)

    // translate filters to function type
    exclude = this._translateFiltersToFunction(exclude, false, false)
    include = this._translateFiltersToFunction(include, false, true)
    excludePath = this._translateFiltersToFunction(excludePath, true, false)
    includePath = this._translateFiltersToFunction(includePath, true, true)

    return {
      exclude: (...params) => exclude(...params) || excludePath(...params),
      include: (...params) => include(...params) && includePath(...params),
    }
  }

  _generateFileReg(fileType) {
    const conf = {
      img: /.png|.jpg|.svg|.webp|.gif|.jpeg|.bmp|.pcx|.tiff|.dxf|.cdr$/,
      js: /.js$/,
      json: /.json$/,
      vue: /.vue$/,
      ts: /.ts$/,
    }
    if (!conf[fileType]) {
      return new RegExp(`${fileType}$`)
    }
    return conf[fileType]
  }

  _translateFiltersToFunction(filter, isPath = false, isInclude = false) {
    if (this._isType(filter, undefined)) {
      return () => isInclude
    }
    if (this._isType(filter, '')) {
      return (...params) => {
        return params[isPath ? 1 : 0] === filter
      }
    }
    if (this._isType(filter, /^/)) {
      return (...params) => {
        return filter.test(params[isPath ? 1 : 0])
      }
    }
    if (this._isType(filter, [])) {
      return (...params) => {
        return filter.some(f => {
          const fn = this._translateFiltersToFunction(f, isPath)

          return fn(...params)
        })
      }
    }
    return filter
  }

  _checkFiltersType(params) {
    if (!this._inType(params, [undefined, [], '', /^/, () => {}])) {
      throw new Error(
        `exclude[Path] | include[Path] expected a [undefined, [], '', /^/, () => {}], but get a ${this._whatType(
          params,
        )}`,
      )
    }
    // if is array type, check every item
    if (this._isType(params, [])) {
      for (const item of params) {
        if (!this._inType(item, ['', /^/, () => {}])) {
          throw new Error(
            `when exclude[Path] | include[Path] is a array type, every item expected a  ['', /^/, () => {}], but get a ${this._whatType(
              item,
            )}`,
          )
        }
      }
    }
  }

  _getFileName(path) {
    const paths = path.substr(0, path.lastIndexOf('.')).split('/')
    let name = paths.slice(-1)[0]

    if (
      name === 'index' &&
      paths.slice(-2)[0] !== undefined &&
      paths.slice(-2)[0] !== '.'
    ) {
      name = paths.slice(-2)[0]
    }

    return name
  }

  _formatFileList(context, noSub) {
    const list = context.keys()

    if (!noSub) return list

    const result = []

    for (const path of list) {
      const pList = path.substr(0, path.lastIndexOf('.')).split('/')
      const name = pList.slice(-1)[0]

      if (pList.length < 3) result.push(path)

      if (pList.length === 3 && name === 'index') result.push(path)
    }

    return result
  }

  _whatType(obj) {
    return Object.prototype.toString.call(obj)
  }

  _isType(...objs) {
    return (
      objs
        .map(item => this._whatType(item) === this._whatType(objs[0]))
        .filter(item => !item).length === 0
    )
  }

  _inType(params, list) {
    if (!this._isType(list, [])) {
      throw new Error(`list expect a ${this._whatType([])}`)
    }
    let flag = false
    for (const i in list) {
      if (this._isType(list[i], params)) {
        flag = true
        break
      }
    }
    return flag
  }
}

export const tinyImport = new TinyImport({
  exclude: ['index', /^(_|:)[\s\S]*/],
})
